/*
 * Copyright (c) 2012-2020 MIRACL UK Ltd.
 *
 * This file is part of MIRACL Core
 * (see https://github.com/miracl/core).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 *   Cryptographic strong random number generator
 *
 *   Unguessable seed -> SHA -> PRNG internal state -> SHA -> random numbers
 *   Slow - but secure
 *
 *   See ftp://ftp.rsasecurity.com/pub/pdfs/bull-1.pdf for a justification
 */

/* Marsaglia & Zaman Random number generator constants */

package core

//import "fmt"

const rand_NK int = 21
const rand_NJ int = 6
const rand_NV int = 8

type RAND struct {
	ira      [rand_NK]uint32 /* random number...   */
	rndptr   int
	borrow   uint32
	pool_ptr int
	pool     [32]byte
}

/* Terminate and clean up */
func (R *RAND) Clean() { /* kill internal state */
	R.pool_ptr = 0
	R.rndptr = 0
	for i := 0; i < 32; i++ {
		R.pool[i] = 0
	}
	for i := 0; i < rand_NK; i++ {
		R.ira[i] = 0
	}
	R.borrow = 0
}

func NewRAND() *RAND {
	R := new(RAND)
	R.Clean()
	return R
}

func (R *RAND) sbrand() uint32 { /* Marsaglia & Zaman random number generator */
	R.rndptr++
	if R.rndptr < rand_NK {
		return R.ira[R.rndptr]
	}
	R.rndptr = 0
	k := rand_NK - rand_NJ
	for i := 0; i < rand_NK; i++ { /* calculate next NK values */
		if k == rand_NK {
			k = 0
		}
		t := R.ira[k]
		pdiff := t - R.ira[i] - R.borrow
		if pdiff < t {
			R.borrow = 0
		}
		if pdiff > t {
			R.borrow = 1
		}
		R.ira[i] = pdiff
		k++
	}

	return R.ira[0]
}

func (R *RAND) sirand(seed uint32) {
	var m uint32 = 1
	R.borrow = 0
	R.rndptr = 0
	R.ira[0] ^= seed
	for i := 1; i < rand_NK; i++ { /* fill initialisation vector */
		in := (rand_NV * i) % rand_NK
		R.ira[in] ^= m /* note XOR */
		t := m
		m = seed - m
		seed = t
	}
	for i := 0; i < 10000; i++ {
		R.sbrand()
	} /* "warm-up" & stir the generator */
}

func (R *RAND) fill_pool() {
	sh := NewHASH256()
	for i := 0; i < 128; i++ {
		sh.Process(byte(R.sbrand() & 0xff))
	}
	W := sh.Hash()
	for i := 0; i < 32; i++ {
		R.pool[i] = W[i]
	}
	R.pool_ptr = 0
}

func pack(b [4]byte) uint32 { /* pack 4 bytes into a 32-bit Word */
	return (((uint32(b[3])) & 0xff) << 24) | ((uint32(b[2]) & 0xff) << 16) | ((uint32(b[1]) & 0xff) << 8) | (uint32(b[0]) & 0xff)
}

/* Initialize RNG with some real entropy from some external source */
func (R *RAND) Seed(rawlen int, raw []byte) { /* initialise from at least 128 byte string of raw random entropy */
	var b [4]byte
	sh := NewHASH256()
	R.pool_ptr = 0

	for i := 0; i < rand_NK; i++ {
		R.ira[i] = 0
	}
	if rawlen > 0 {
		for i := 0; i < rawlen; i++ {
			sh.Process(raw[i])
		}
		digest := sh.Hash()

		/* initialise PRNG from distilled randomness */

		for i := 0; i < 8; i++ {
			b[0] = digest[4*i]
			b[1] = digest[4*i+1]
			b[2] = digest[4*i+2]
			b[3] = digest[4*i+3]
			R.sirand(pack(b))
		}
	}
	R.fill_pool()
}

/* get random byte */
func (R *RAND) GetByte() byte {
	r := R.pool[R.pool_ptr]
	R.pool_ptr++
	if R.pool_ptr >= 32 {
		R.fill_pool()
	}
	return byte(r & 0xff)
}

/* test main program */
/*
func main() {
	var raw [100]byte
	rng:=NewRAND()

	rng.Clean()
	for i:=0;i<100;i++ {raw[i]=byte(i)}

	rng.Seed(100,raw[:])

	for i:=0;i<1000;i++ {
		fmt.Printf("%03d ",rng.GetByte())
	}
}
*/
